//
//  NSObject+kz_global.m
//  KZTools_ObjectC
//
//  Created by 朱凯 on 2018/11/26.
//  Copyright © 2018年 zhukai. All rights reserved.
//

#import "NSObject+kz_global.h"

#import <objc/runtime.h>
#import <Messages/Messages.h>

const char *KZDescriptionApplicativedClassesKey = "KZDescriptionApplicativedClassesKey";
static NSArray<Class> *_applicativedClasses;

@implementation NSObject (kz_global)

+ (void)load {
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method new = class_getInstanceMethod(self, @selector(kz_description));
        Method old = class_getInstanceMethod(self, @selector(description));
        BOOL isAddedMethod = class_addMethod(self, @selector(description), method_getImplementation(new), method_getTypeEncoding(new));
        if (isAddedMethod) {
            class_replaceMethod(self, @selector(kz_description), method_getImplementation(old), method_getTypeEncoding(old));
        } else {
            method_exchangeImplementations(old, new);
        }
    });
}

- (NSString *)kz_description {
    
    _applicativedClasses = objc_getAssociatedObject([NSObject class], KZDescriptionApplicativedClassesKey);
    __block BOOL contains = false;
    [_applicativedClasses enumerateObjectsUsingBlock:^(Class _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([self isKindOfClass:obj]) {
            contains = true;
            *stop = true;
        }
    }];
    if (!_applicativedClasses || !contains) {
        return [self kz_description];
    }
    
    NSMutableString *result = [[NSMutableString alloc] initWithFormat:@"%@ --- with Objc value list\n", [self kz_description]];
    
    unsigned int count;
    Ivar *list = class_copyIvarList(self.class, &count);
    for (int i = 0; i < count; i ++) {
        
        Ivar ivar = list[i];
        const char *type = ivar_getTypeEncoding(ivar);
        const char *name = ivar_getName(ivar);
        [self operateIvarsToString:result type:type selName:name];
    }
    free(list);
    return result.copy;
}

+ (void)kz_descriptionApplyTo:(NSArray<Class> *)classes {
    objc_setAssociatedObject([NSObject class], KZDescriptionApplicativedClassesKey, classes, OBJC_ASSOCIATION_COPY_NONATOMIC);
    _applicativedClasses = objc_getAssociatedObject([NSObject class], KZDescriptionApplicativedClassesKey);
}


/**
 *  系统的 performSelector 不支持参数或返回值为非对象的 selector 的调用，所以增加了对应的方法，支持对象和非对象返回的 selector。
 *  @param selector 要被调用的方法名
 *  @param returnValue selector 的返回值的指针地址
 *  @param args 参数数组
 */
- (void)kz_performSelector:(SEL)selector returnValue:(void *)returnValue argsFromArray:(NSArray *)args {
    
    // 方法签名
    NSMethodSignature *signature = [self methodSignatureForSelector:selector];
    // 方法引用
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    invocation.target = self;
    invocation.selector = selector;
    
    // 判断个数，不导致crash
    NSInteger count = MIN(signature.numberOfArguments - 2, args.count);
    for (int i = 0; i < count; i ++) {
        // 参数
        id arg = args[i];
        // 参数类型
        const char *argType = [signature getArgumentTypeAtIndex:i + 2];
        // 参数类型是oc对象，则和数组元素匹配，直接设置参数
        if (strcmp(argType, "@") == 0) {
            [invocation setArgument:(void *)&arg atIndex:i + 2];
        }
        // 如果参数类型不是oc对象，暂时只支持int，float，bool参数的，则数组元素必须为NSNumber或NSString类型
        else {
            NSAssert([arg isKindOfClass:NSNumber.class] || [arg isKindOfClass:NSString.class], @"对于包含非oc类型的方法参数，传入数组元素必须为NSString或NSNumber类型");
            if (strcmp(argType, "i") == 0) {
                int temp = [arg intValue];
                [invocation setArgument:(void *)&temp atIndex:i + 2];
            } else if (strcmp(argType, "f") == 0) {
                float temp = [arg floatValue];
                [invocation setArgument:(void *)&temp atIndex:i + 2];
            } else if (strcmp(argType, "B") == 0) {
                BOOL temp = [arg boolValue];
                [invocation setArgument:(void *)&temp atIndex:i + 2];
            } else {
                NSString *desc = [NSString stringWithFormat:@"暂不支持调用含%s参数类型的方法", argType];
                NSAssert(nil, desc);
            }
        }
        
    }
    
    [invocation invoke];
    
    if (returnValue) {
        [invocation getReturnValue:returnValue];
    }
    
}

// 使用 block 遍历当前实例的所有方法，父类的方法不包含在内
- (void)kz_enumrateInstanceMethodsUsingBlock:(void (^)(SEL selector))block {
    [NSString kz_enumrateInstanceMethodsOfClass:self.class usingBlock:block];
}

// 使用 block 遍历指定的某个类的实例方法，该类的父类方法不包含在内
+ (void)kz_enumrateInstanceMethodsOfClass:(Class)aClass usingBlock:(void (^)(SEL selector))block {
    unsigned int count;
    Method *list = class_copyMethodList(aClass, &count);
    for (int i = 0; i < count; i ++) {
        Method method = list[i];
        SEL selector = method_getName(method);
        if (block) {
            block(selector);
        }
    }
    free(list);
}



#pragma mark - 辅助方法
//
- (void)operateIvarsToString:(NSMutableString *)result type:(const char *)type selName:(const char *)name {
    
    NSString *getSelName = [[NSString stringWithFormat:@"%s", name] stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:@""];
    SEL sel = NSSelectorFromString(getSelName);
    
    // a object
    NSString *typeStr = [NSString stringWithFormat:@"%s", type];
    if ([typeStr containsString:@"@"]) {
        id value;
        [self kz_performSelector:sel returnValue:&value argsFromArray:nil];
        [result appendFormat:@"%@: %@\n", getSelName, value];
    }
    // a char
    else if (strcmp(type, "c") == 0) {
        char value;
        [self kz_performSelector:sel returnValue:&value argsFromArray:nil];
        [result appendFormat:@"%@: %c\n", getSelName, value];
    }
    else if (strcmp(type, "C") == 0) {
        unsigned char value;
        [self kz_performSelector:sel returnValue:&value argsFromArray:nil];
        [result appendFormat:@"%@: %c\n", getSelName, value];
    }
    // a int
    else if (strcmp(type, "i") == 0) {
        int value;
        [self kz_performSelector:sel returnValue:&value argsFromArray:nil];
        [result appendFormat:@"%@: %i\n", getSelName, value];
    }
    else if (strcmp(type, "I") == 0) {
        unsigned int value;
        [self kz_performSelector:sel returnValue:&value argsFromArray:nil];
        [result appendFormat:@"%@: %u\n", getSelName, value];
    }
    else if (strcmp(type, "s") == 0) {
        short value;
        [self kz_performSelector:sel returnValue:&value argsFromArray:nil];
        [result appendFormat:@"%@: %hd\n", getSelName, value];
    }
    else if (strcmp(type, "S") == 0) {
        unsigned short value;
        [self kz_performSelector:sel returnValue:&value argsFromArray:nil];
        [result appendFormat:@"%@: %hu\n", getSelName, value];
    }
    else if (strcmp(type, "l") == 0) {
        long value;
        [self kz_performSelector:sel returnValue:&value argsFromArray:nil];
        [result appendFormat:@"%@: %li\n", getSelName, value];
    }
    else if (strcmp(type, "L") == 0) {
        unsigned long value;
        [self kz_performSelector:sel returnValue:&value argsFromArray:nil];
        [result appendFormat:@"%@: %lu\n", getSelName, value];
    }
    else if (strcmp(type, "q") == 0) {
        long long value;
        [self kz_performSelector:sel returnValue:&value argsFromArray:nil];
        [result appendFormat:@"%@: %lli\n", getSelName, value];
    }
    //
    else if (strcmp(type, "Q") == 0) {
        unsigned long long value;
        [self kz_performSelector:sel returnValue:&value argsFromArray:nil];
        [result appendFormat:@"%@: %llu\n", getSelName, value];
    }
    // a float
    else if (strcmp(type, "f") == 0) {
        float value;
        [self kz_performSelector:sel returnValue:&value argsFromArray:nil];
        [result appendFormat:@"%@: %.2f\n", getSelName, value];
    }
    // a double
    else if (strcmp(type, "d") == 0) {
        double value;
        [self kz_performSelector:sel returnValue:&value argsFromArray:nil];
        [result appendFormat:@"%@: %.4lf\n", getSelName, value];
    }
    // a bool
    else if (strcmp(type, "B") == 0) {
        BOOL value;
        [self kz_performSelector:sel returnValue:&value argsFromArray:nil];
        [result appendFormat:@"%@: %@\n", getSelName, value ? @"true" : @"false"];
    }
    // a char *
    else if (strcmp(type, "*") == 0) {
        char *value;
        [self kz_performSelector:sel returnValue:&value argsFromArray:nil];
        [result appendFormat:@"%@: %s\n", getSelName, value];
    }
    // a Class
    else if (strcmp(type, "#") == 0) {
        Class value;
        [self kz_performSelector:sel returnValue:&value argsFromArray:nil];
        [result appendFormat:@"%@: %@\n", getSelName, value];
    }
    // a SEL
    else if (strcmp(type, ":") == 0) {
        SEL value;
        [self kz_performSelector:sel returnValue:&value argsFromArray:nil];
        [result appendFormat:@"%@: %@\n", getSelName, NSStringFromSelector(value)];
    }
}
@end
